<?php
/** 
 * EGP Framework
 *
 * LICENSE
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 * @copyright  Copyright (c) 2008 (http://www.tblog.com.cn)
 * @license    http://www.gnu.org/licenses/gpl.html     GPL 3
 */

!defined('EGP_LIB_DIR') && die('Access Deny!');

/**
 * ACL 权限控制
 *
 * @package    classes
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 * @copyright  Copyright (c) 2008 (http://www.tblog.com.cn)
 * @license    http://www.gnu.org/licenses/gpl.html     GPL 3
 */
class EGP_Acl
{

    const TYPE_ALLOW = 'TYPE_ALLOW';
    const TYPE_DENY  = 'TYPE_DENY';

    /**
     * 角色
     *
     * @var array
     */
    protected $_roles = array();

    /**
     * 资源
     *
     * @var array
     */
    protected $_resources = array();

    /**
     * 规则
     *
     * @var array
     */
    protected $_rules = array();

    /**
     * 添加角色
     *
     * @param  string  $role
     * @return EGP_Acl
     * @throws EGP_Exception
     */
    public function addRole($role)
    {
        if ($this->hasRole($role))
            throw new EGP_Exception("角色 '$role' 已经存在");
        $this->_roles[] = $role;
        return $this;
    }

    /**
     * 查询指定的角色是否已添加
     *
     * @param  string  $role
     * @return EGP_Acl
     */
    public function hasRole($role)
    {
        return in_array($role, $this->_roles);
    }

    /**
     * 移除角色
     *
     * @param  string  $role
     * @return EGP_Acl
     */
    public function removeRole($role)
    {
        $key = array_search($role, $this->_roles);
        if ($key !== false) {
            unset($this->_roles[$key]);
            unset($this->_rules[$role]);
        }
        return $this;
    }

    /**
     * 移除所有角色
     *
     * @return EGP_Acl
     */
    public function removeRoleAll()
    {
        $this->_roles = array();
        $this->_rules = array();
        return $this;
    }

    /**
     * 添加资源
     *
     * @param  string  $resource
     * @return EGP_Acl
     * @throws EGP_Exception
     */
    public function add($resource)
    {
        if ($this->has($resource))
            throw new EGP_Exception("资源 '$resource' 已经存在");
        $this->_resources[] = $resource;
        return $this;
    }

    /**
     * 查询是否指定的资源已添加
     *
     * @param  string  $resource
     * @return EGP_Acl
     */
    public function has($resource)
    {
        return in_array($resource, $this->_resources);
    }

    /**
     * 移除资源
     *
     * @param  string  $resource
     * @return EGP_Acl
     */
    public function remove($resource)
    {
        $key = array_search($resource, $this->_resources);
        if ($key !== false) {
            unset($this->_resources[$key]);
            foreach ($this->_rules as &$rule)
                unset($rule[$resource]);
        }
        return $this;
    }

    /**
     * 移除所有资源
     *
     * @return EGP_Acl
     */
    public function removeAll()
    {
        $this->_resources = array();
        foreach ($this->_rules as &$role)
            $role = array();
        return $this;
    }

    /**
     * 设定一条允许通过的规则
     *
     * @param  string|array  $roles
     * @param  string|array  $resources
     * @param  string|array  $privileges
     * @return EGP_Acl
     */
    public function allow($roles, $resources, $privileges = null)
    {
        return $this->setRule(self::TYPE_ALLOW, $roles, $resources, $privileges);
    }

    /**
     * 设定一条禁止通过的规则
     *
     * @param  string|array  $roles
     * @param  string|array  $resources
     * @param  string|array  $privileges
     * @return EGP_Acl
     */
    public function deny($roles, $resources, $privileges = null)
    {
        return $this->setRule(self::TYPE_DENY, $roles, $resources, $privileges);
    }

    /**
     * 设定一条规则
     *
     * @param  string        $type
     * @param  string|array  $roles
     * @param  string|array  $resources
     * @param  string|array  $privileges
     * @return EGP_Acl
     * @throws EGP_Exception
     */
    public function setRule($type, $roles, $resources, $privileges = null)
    {
        if (self::TYPE_ALLOW !== $type && self::TYPE_DENY !== $type)
            throw new EGP_Exception("不支持的规则类型，必须是' " . self::TYPE_ALLOW .
                                    " '或' " . self::TYPE_DENY . " '");

        //分析角色
        $roles === '*' ?
            $roles = $this->_roles :
            (!is_array($roles) && $roles = array($roles));

        //分析资源
        $resources === '*' ?
            $resources = $this->_resources :
            (!is_array($resources) && $resources = array($resources));

        //分析权限
        null == $privileges && $privileges = array('access');
        !is_array($privileges) && $privileges = array($privileges);
        $temp = array();
        foreach ($privileges as $privilege) {
            $temp[$privilege] = (self::TYPE_ALLOW === $type);
        }
        $privileges = $temp;
        unset($temp);

        //设定规则
        $rules = array();
        foreach ($resources as $resource) {
            if (!$this->hasResource($resource))
                throw new EGP_Exception("资源 '$resource' 不存在");
            foreach ($roles as $role) {
                if (!$this->hasRole($role))
                    throw new EGP_Exception("角色 '$role' 不存在");
                $rules[$role][$resource] = $privileges;
            }
        }
        $this->_rules = array_merge_deep($this->_rules, $rules);

        return $this;
    }

    /**
     * 判断指定的角色对资源是否拥有正确的访问权限
     *
     * @param  string                $role
     * @param  string                $resource
     * @param  string                $privilege
     * @param  EGP_Acl_Assert_Interface  $assert
     * @return boolean
     * @throws EGP_Exception
     */
    public function isAllow($role, $resource, $privilege = 'access',
                            EGP_Acl_Assert_Interface $assert = null)
    {
        if (!$this->hasRole($role))
            throw new EGP_Exception("角色 '$role' 不存在");

        if (!$this->hasResource($resource))
            throw new EGP_Exception("资源 '$resource' 不存在");

        if (isset($this->_rules[$role]) &&
            isset($this->_rules[$role][$resource]) &&
            isset($this->_rules[$role][$resource][$privilege])) {
            $assert = (null != $assert) ? $assert->assert() : true;
            return $this->_rules[$role][$resource][$privilege] && $assert;
        } else {
            return false;
        }
    }

}